#include "d3d.h"
#include "..\common\def.h"
#include "..\engine\engine.h"

#include ".\shaders\d3d_depthmapshader.h"
#include ".\shaders\d3d_lightshader.h"
#include ".\shaders\d3d_particleshader.h"
#include ".\shaders\d3d_screenshader.h"
#include ".\shaders\d3d_watershader.h"
#include ".\shaders\d3d_maskshader.h"
#include ".\shaders\d3d_reflectshader.h"

const char* gGeometryFilename		= "Common.fx"; 
const char* gLightFilename			= "Deferred.fx"; 
const char* gScreenImageFilename	= "Sprite.fx"; 
const char* gPostProcessFilename	= "Postprocess.fx"; 
const char* gParticleFilename		= "ParticleSystem.fx"; 
const char* gDepthbufferFilename	= "Depthmap.fx"; 
const char* gSkydomeFilename		= "Skybox.fx"; 

/**************************************
 * Enables the client application to 
 * register custom shader code with the
 * render engine for custom rendering effects.
 * The filename specifies the external code
 * source. An identifier within the shader file
 * specifies the shader implementation and thus, 
 * the way in which the code is setup/configured 
 * prior to rendering geometry with that shader
 **************************************/
HRESULT	CD3D9Device::CreateShader(D3DShader **pOut, const string& szFilename)
{
	HRESULT hr;
	string shaderType;
	D3DShader* pShader = NULL; 
	LPD3DXBUFFER pErrors = NULL; 
	LPD3DXEFFECT pEffect = NULL;

	do {

		if(ISNULL(pOut) || !szFilename.IsValid()) {
			hr = E_INVALIDARG;
			break;
		}

		if(SUCCEEDED(SearchForResource(szFilename, (IResource**) &pShader, RESOURCE_SHADER)))
		{
			if(pShader) 
			{
				*pOut = pShader; 
				return S_OK; 
			}
		}

		//Attempt to determine the implementation required by the shader
		//If the shader file dosen't exist simply use the default shader
		const string filepath = CEngine::GetInstance()->FindFilepath(szFilename); 
		if(filepath.IsValid()) {

			if(D3DXCreateEffectFromFile(m_pD3DDevice,filepath.c_str(),NULL,NULL, NULL, D3DShader::m_pEffectPool,&pEffect,&pErrors) != D3D_OK) {
				hr = E_FAIL;
				EE_LOGERROR((pErrors ? (char*)pErrors->GetBufferPointer() : "Unknown shader error"));
				break;
			}

			D3DXHANDLE pTypeHandle = pEffect->GetParameterByName(NULL, "g_type");
			if(pTypeHandle) {
				LPCSTR pTypeStr = NULL;
				pEffect->GetString(pTypeHandle, &pTypeStr);
				if(pTypeStr)
					shaderType = string(pTypeStr);
			}

			if(shaderType.equals("D3DWaterShader")) {
				pShader= EE_NEW D3DWaterShader(this); 
			}
			else if(shaderType.equals("D3DReflectShader")) {
				pShader= EE_NEW D3DReflectShader(this); 
			}
			else if(shaderType.equals("D3DShader")) {
				pShader= EE_NEW D3DShader(this); 
			} 
			else if(shaderType.equals("D3DDepthMapShader")) {
				pShader= EE_NEW D3DDepthMapShader(this); 
			}
			else if(shaderType.equals("D3DLightShader")) {
				pShader= EE_NEW D3DLightShader(this); 
			}
			else if(shaderType.equals("D3DParticleShader")) {
				pShader= EE_NEW D3DParticleShader(this); 
			}
			else if(shaderType.equals("D3DScreenSpriteShader")) {
				pShader= EE_NEW D3DScreenSpriteShader(this); 
			}
			else if(shaderType.equals("D3DSpriteShader")) {
				pShader= EE_NEW D3DSpriteShader(this); 
			}
			else if(shaderType.equals("D3DScreenShader")) {
				pShader= EE_NEW D3DScreenShader(this);
			}
			else if(shaderType.equals("D3DMaskShader")) {
				pShader= EE_NEW D3DMaskShader(this); 
			}
			else {
				EE_LOGERROR("Warning: could not locate shader type data.");
			}

			if(ISNULL(pShader)) {
				hr = E_UNEXPECTED;
				break;
			}

			pShader->m_pEffect = pEffect;

			if(FAILED(pShader->Create(szFilename))) {
				EE_LOGERROR("Shader Create() failed."); 
				hr = E_FAIL;
				break;
			}
			
			m_Shaders.Add(pShader); 
			m_Shaders.Sort(D3DShader::funcRenderOrderSort);
			*pOut = pShader; 
		}
		else {

			*pOut = m_pDefaultShader;

		}

		return S_OK; 
	
	}while(false);

	if(pShader) {
		DeregisterResource(pShader);
		EE_SAFEDELETE(pShader); 
	}

	if(pEffect) {
		pEffect->Release();
		pEffect = NULL;
	}

	if(pErrors) {
		pErrors->Release();
		pErrors = NULL;
	}	

	*pOut = NULL; 
	return hr; 
}

/**************************************
 *Attempts to load the default shaders
 *that can be used with this device for
 *rendering purposes
 **************************************/
HRESULT CD3D9Device::createDefaultShaders()
{
	HRESULT hr;
	D3DShader	*pGeometryDefault	= NULL; 
	D3DLightShader	*pLightDefault	= NULL; 
	D3DScreenShader	*pScreenDefault		= NULL; 
	D3DShader	*pGUIDefault			= NULL; 
	D3DShader	*pSkyShader	= NULL; 
	D3DDepthMapShader *pDepthShader = NULL; 
	D3DScreenSpriteShader *pScreenSpriteShader = NULL; 
	D3DSpriteShader *pPointSpriteDefault = NULL; 
	D3DParticleShader* pParticleDefault = NULL;

	do {

		if(FAILED(D3DShader::CreateEffectPool())) {
			hr = E_FAIL;
			break;
		}

		//Load the default shader set
		if(FAILED(CreateShader(&pGeometryDefault, gGeometryFilename))) {
			EE_LOGERROR("Failed to create default geometry shader.")
			hr = E_FAIL;
			break;
		}

		if(FAILED(CreateShader((D3DShader**)&pLightDefault, gLightFilename))) {
			EE_LOGERROR("Failed to create default light shader.")
			hr = E_FAIL;
			break;
		}

		if(FAILED(CreateShader((D3DShader**)&pScreenSpriteShader, "Sprite.fx"))) {
			hr = E_FAIL;
			break;
		}

		if(FAILED(CreateShader((D3DShader**)&pParticleDefault, "ParticleSystem.fx"))) {
			hr = E_FAIL;
			break;
		}
		
		if(FAILED(CreateShader((D3DShader**)&pPointSpriteDefault, "PointSprite.fx"))) {
			hr = E_FAIL;
			break;
		}

		if(FAILED(CreateShader((D3DShader**)&pScreenDefault, gPostProcessFilename))) {
			hr = E_FAIL;
			break;
		}

		if(FAILED(CreateShader((D3DShader**)&pDepthShader, gDepthbufferFilename))) {
			hr = E_FAIL;
			break;
		}

		if(FAILED(CreateShader((D3DShader**)&pSkyShader, gSkydomeFilename))) {
			hr = E_FAIL;
			break;
		}

		//Don't want to iterate this shader twice in the render loop
		m_Shaders.Remove(pScreenDefault); 	
		m_Shaders.Remove(pLightDefault); 	
		m_Shaders.Remove(pSkyShader); 	
		m_Shaders.Remove(pDepthShader); 
		m_Shaders.Remove(pScreenSpriteShader); 

		m_pLightShader		= pLightDefault; 
		m_pDefaultShader	= pGeometryDefault; 
		m_pScreenShader		= pScreenDefault; 
		m_pSkyShader		= pSkyShader; 
		m_pDepthShader		= pDepthShader; 
		m_pScreenSpriteShader	= pScreenSpriteShader; 

		return S_OK; 

	}while(false);

	DeregisterResource(pGeometryDefault);
	DeregisterResource(pLightDefault);
	DeregisterResource(pScreenDefault);
	DeregisterResource(pGUIDefault);
	DeregisterResource(pSkyShader);
	DeregisterResource(pDepthShader);
	DeregisterResource(pScreenSpriteShader);
	DeregisterResource(pPointSpriteDefault);
	DeregisterResource(pParticleDefault);

	EE_SAFEDELETE(pGeometryDefault);
	EE_SAFEDELETE(pLightDefault);
	EE_SAFEDELETE(pScreenDefault);
	EE_SAFEDELETE(pGUIDefault);
	EE_SAFEDELETE(pSkyShader);
	EE_SAFEDELETE(pDepthShader);
	EE_SAFEDELETE(pScreenSpriteShader);
	EE_SAFEDELETE(pPointSpriteDefault);
	EE_SAFEDELETE(pParticleDefault);

	return hr;
}

/**************************************
 *Attempts to load the default shaders
 *that can be used with this device for
 *rendering purposes
 **************************************/
HRESULT CD3D9Device::releaseDefaultShaders()
{
	D3DShader* pCurrent = NULL; 
	HRESULT hr;

	if(FAILED(DeregisterResource(m_pDefaultShader)))	{ hr = E_FAIL; }
	if(FAILED(DeregisterResource(m_pDepthShader)))	{ hr = E_FAIL; }
	if(FAILED(DeregisterResource(m_pScreenSpriteShader)))	{ hr = E_FAIL; }
	if(FAILED(DeregisterResource(m_pScreenShader)))	{ hr = E_FAIL; }
	if(FAILED(DeregisterResource(m_pSkyShader)))		{ hr = E_FAIL; }
	if(FAILED(DeregisterResource(m_pLightShader)))	{ hr = E_FAIL; }
	//if(FAILED(DeregisterResource(m_pScreenColorOverride))) { hr = E_FAIL; }
	//if(FAILED(DeregisterResource(pPointSpriteDefault)))	{ hr = E_FAIL; }
	//if(FAILED(DeregisterResource(pParticleDefault)))	{ hr = E_FAIL; }

	EE_SAFEDELETE(m_pDefaultShader);
	EE_SAFEDELETE(m_pDepthShader);
	EE_SAFEDELETE(m_pScreenSpriteShader);
	EE_SAFEDELETE(m_pScreenShader);
	EE_SAFEDELETE(m_pSkyShader);
	EE_SAFEDELETE(m_pLightShader);
	EE_SAFEDELETE(m_pScreenColorOverride);

	if(FAILED(D3DShader::ReleaseEffectPool())) {
		hr = E_FAIL;
	}

	if(FAILED(hr)) {
		EE_LOGERROR("An error(s) occoured while releasing default shader resources.")
	}
	return hr;
}
